/*--
 * Core.cs: Core to the entire Metashell system.
 *--
 * For more information, please visit the Metashell Project site:
 *   http://code.google.com/p/metashell/
 *--
 * (c) 2006 Christopher E. Granade.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; exactly version 2
 * of the License applies to this software.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
 
#region Using Declarations
	
	using System;
	using System.Collections.Generic;
	using System.IO;
	using System.Reflection;
	using System.Xml;
	using System.Xml.Xsl;
	
#endregion

namespace Metashell.Core {

	public class Core {
		
		#region Private Member Variables
		
			string curdir;
			
			Dictionary<string, ICommand> commands = new Dictionary<string, ICommand>();
			
			// Since this one is an ugly type string, let me explain:
			// This type provides a map from a pair of strings to a compiled XSLT transformation.
			// Each of the strings corresponds to a format specification.
			Dictionary<Utility.Pair<string, string>, XslCompiledTransform> transforms = null;
			
		
		#endregion
		
		#region Delegate Types
		
			/// Delegate type to be called when an error needs to be reported to the user.
			public delegate void ErrorEvent(string err);
			
			/// Delegate type for simple events.
			public delegate void SimpleEvent();
		
		#endregion
		
		#region Events
		
			/// Called when a frontend needs to handle an error.
			public event ErrorEvent OnError;
			
			/// Called when user action triggers an exit.
			public event SimpleEvent OnExit;
		
		#endregion
		
		#region Properties
		
			/// Returns the current working directory.
			/// Commands can set this directly, but a
			/// <see cref="DirectoryNotFoundException" />
			/// will be thrown if it is set to an invalid path.
			public string CurrentDirectory {
				get { return this.curdir; }
				set {
					if (Directory.Exists(value)) {
						this.curdir = value;
					} else {
						throw new DirectoryNotFoundException();
					}
				}
			}
			
			/// Returns the directory where the Core program files are stored.
			public string CoreAppDirectory {
	            get { return Configuration.GetCoreAppDirectory(); }
            }
            
            /// Returns the directory where resources for commands should be
            /// stored.
            public string ResourceDirectory {
                get { return Configuration.GetResourceDirectory(); }
            }
		
		#endregion
		
		#region Private Methods
		
			private Metadoc ProcessCmd(string cmd, Metadoc indoc) {
				
				ICommand ic = null;
				Metadoc outdoc;
				
				// Break off the command name from the parameters.
				// First, check to see if params even /exist/ by looking for internal spaces.
				
				string cmdname, pms;
				
				if (cmd.Trim().IndexOf(' ') != -1)  {
					cmdname = cmd.Substring(0, cmd.IndexOf(' '));
				    pms = cmd.Substring(cmd.IndexOf(' ') + 1);
				} else {
					cmdname = cmd.Trim();
					pms = "";
				}
				
				// Try and find the cmdname.
				try {
					ic = commands[cmdname];
				} catch (Exception e) {
					throw new Exception("Command " + cmdname + " not found. Is the correct plugin installed?");
				}
				
				// If we're here, pass along.
				ic.Run(pms, indoc, out outdoc);
				
				return outdoc;
				
			}
			
			private void loadCmdFromAssembly(string assmpath) {
				
				Assembly assm = null;
				
				try {
					assm = Assembly.LoadFile(assmpath);
				} catch (Exception e) {
					System.Console.WriteLine("This error should never occur.");
					return;
				}
				
				// Get the types from the assembly.
				Type[] types = assm.GetTypes();
				
				foreach (Type type in types) {
					// Check if ICommand is inherited.
					if (type.GetInterface("ICommand") != null) {
						// OK! We found a good one. Now get its default constructor.
						ConstructorInfo ci = null;
						ICommand ic = null;
						
						try {
						
							ci = type.GetConstructor(Type.EmptyTypes);
							
							object o = ci.Invoke(null);
							ic = (Metashell.Core.ICommand)o;
							
							// Register ourselves with the command.
							ic.RegisterCore(this);
							
							// Now add it to the list of commands.
							commands.Add(ic.name, ic);
							
						} catch (InvalidCastException ice) {
						
							RaiseError(ice.Message);
							System.Console.WriteLine(AppDomain.CurrentDomain);
						
						}
						
						
					}
				}
				
			}
			
			private void loadCommands(List<string> assemblies) {
			
				foreach (string assmpath in assemblies) {
					loadCmdFromAssembly(assmpath);
				}
			
			}
			
			private XmlDocument convertXML(Metadoc indoc, string toformat) {
				
				// First, if either the from format or the to format is
				// null, then just return null already.
				if ((toformat == "null") || (indoc.format == "null")) {
					return null;
				}
				
				// Next, check if the input and output formats are the same. If so,
				// pass through.
				if (toformat == indoc.format) {
					return indoc.data;
				}
				
				XslCompiledTransform xct = null;
				
				// Load the transform.
				Utility.Pair<string, string> p;
				p.First = indoc.format;
				p.Second = toformat;
				
				try {
					xct = this.transforms[p];
				} catch (KeyNotFoundException knfe) {
					OnError("Could not find XSL transform from " +
						p.First + " to " + p.Second + ".");
						
						return indoc.data;
				}
				
				System.IO.StringWriter sw = new StringWriter();
				xct.Transform(indoc.data, null, sw);
				
				XmlDocument xd = new XmlDocument();
				
				// This might fail, so wrap in try/catch.
				try {
					xd.LoadXml(sw.ToString());
				} catch (Exception e) {
					RaiseError("Problem during transformation: " + e.Message);
				}
				
				return xd;
				
			}
		
		#endregion
		
		#region Constructors
		
			/// Initialize the various things required by the Core.
			public Core() {
				
				// Grab stuff from Configuration.
				curdir = Configuration.GetHomeDirectory();
				List<string> assembiles = Configuration.GetCommandPluginPaths();
				
				// Now load plugins and transforms.
				loadCommands(assembiles);
				transforms = Configuration.GetTransforms();
				
			}
		
		#endregion
		
		#region Public Methods
		
			/// <summary>
			/// Returns the value of a preference set by the user.
			/// Preferences are named like the prefs.js file used by
			/// the Mozilla project.
			/// </summary>
			/// <param name="pref">Name of the preference to retrieve.</param>
			/// <param name="defval">Value to return if the preference doesn't
			/// 	exist.</param>
			public string GetPreference(string pref, string defval) {
				return Configuration.GetPreference(pref, defval);
			}
			
			/// <summary>
			/// Sets the value of a user preference. If the preference does
			/// not exist, then it is created.
			/// Preferences are named like the prefs.js file used by
			/// the Mozilla project.
			/// </summary>
			/// <param name="pref">Name of the preference to be set.</param>
			/// <param name="newval">Value to set the preference to.</param>
			public void SetPreference(string pref, string newval) {
				Configuration.SetPreference(pref, newval);
			}
		
			/// <summary>
			/// Call to reload the XSL transformations used by the Core.
			/// </summary>
			public void ReloadTransforms() {
				transforms = Configuration.GetTransforms();
			}
		    
		    /// <summary>
		    /// Notifies the Core that an error has occured.
		    /// </summary>
		    /// <param name="err">String describing the error.</param>
		    public void RaiseError(string err) {
		    	if (OnError != null) {
		    		OnError(err);
		    	}
		    	
		    	
		    		System.Console.WriteLine(err);
		    }
		    
		    /// <summary>
		    /// Translates a resource path into a system path.
		    /// </summary>
		    /// <param name="resource">Path to the desired resource.</param>
		    /// <returns>A string representing the system path to the
		    /// 	desired resource.</returns>
            public string GetResourcePath(string resource) {
                return this.ResourceDirectory +
                	Path.DirectorySeparatorChar.ToString() + resource;
            }
		
			/// <summary>
			///	Tells Metashell to exit.
			/// </summary>
			public void DoExit() {
				this.OnExit();
			}
			
			/// <summary>
			/// Processes a command line and returns it as an XMLDocument.
			/// Takes a command line and a requested format, then transforms whatever output to that format
			/// if possible and throws an exception if not.
			/// </summary>
			/// <param name="cmdline">String representing user input
			///		to be processed.</param>
			/// <param name="format">String representing the desired output format
			/// 	as specified by <see cref="MetadocFormats" />.</param>
			public XmlDocument ProcessLine(string cmdline, string format) {
			
				// Keep some idea of state.
				Metadoc indoc;
				indoc.data = null;
				indoc.format = MetadocFormats.Null;
			
				// Break at the pipe character; tokenize.
				foreach (string cmd in cmdline.Split('|')) {
				
					string cmdtrim = cmd.TrimStart();
					System.Console.WriteLine("*" + cmdtrim + "*");
				
					try {
						indoc = ProcessCmd(cmdtrim, indoc);
					} catch (Exception e) {
						RaiseError("Error occured while processing " + cmdtrim + ": " + e.Message);
					}
					
				}
				
				return convertXML(indoc, format);
			
			}
			
		#endregion
		
	}

}